/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version queue.c 0.0.2
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * Event queue
 * Based on BS.Queue project
 */

#include "bspd.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <event.h>

struct event_item *event_pool;
extern struct event_base *ev_base;
static struct event hb_event;
struct clock_cb_func clock_cb_list[CLOCK_CB_FUNC_POOL_SIZE];
unsigned int total_events;
unsigned int active_events;
unsigned long long sgn;

/* Initializer */
int queue_init()
{
	event_pool = (struct event_item *) malloc(BASE_QUEUE_EVENT * sizeof(struct event_item));
	memset(clock_cb_list, 0, sizeof(struct clock_cb_func *) * CLOCK_CB_FUNC_POOL_SIZE);
	
	if (!event_pool)
	{
		return RTN_MEMORY_ERROR;
	}
	
	memset(event_pool, 0, BASE_QUEUE_EVENT * sizeof(struct event_item));
	total_events = BASE_QUEUE_EVENT;
	active_events = 0;
	sgn = 0;

	fprintf(stderr, "Timer clock initialized\n");
	log_add("Queue service started");

	if (BRIDGE_MODE_CENTER == bridge_get_mode() || BRIDGE_MODE_UNITED == bridge_get_mode())
	{
		queue_clock_cb_func_add(queue_timer_cb, NULL);
	}
	
	queue_clock_handler(0, 0, 0);
	
	return RTN_SUCCESS;
}

/* Clear all events */
void queue_clear()
{
	int i;
	
	for (i = 0; i < total_events; i ++)
	{
		queue_del_item(i);
	}
	
	active_events = 0;
	
	return;
}

/* Clock event */
void queue_clock_handler(int fd, short ev, void *arg)
{
	struct timeval t = {.tv_sec = 1, .tv_usec = 0};
	static char initialized = 0x0;
	
	if (initialized)
	{
		evtimer_del(&hb_event);
	}
	
	else
	{
		initialized = 0x1;
	}

	evtimer_set(&hb_event, queue_clock_handler, 0);
	event_base_set(ev_base, &hb_event);
	evtimer_add(&hb_event, &t);

	queue_clock_callback();
	
	return;
}

/* Clock event callback function */
void queue_clock_callback()
{
	int i;

	for (i = 0; i < CLOCK_CB_FUNC_POOL_SIZE; i ++)
	{
		if (clock_cb_list[i].fn)
		{
			clock_cb_list[i].fn(clock_cb_list[i].arg);
		}
	}

	return;
}

/* Add a callback function into clock callback list */
int queue_clock_cb_func_add(void (*fn) (void *), void *arg)
{
	int i;

	for (i = 0; i < CLOCK_CB_FUNC_POOL_SIZE; i ++)
	{
		if (!clock_cb_list[i].fn)
		{
			// An empty slot
			clock_cb_list[i].fn = fn;
			clock_cb_list[i].arg = arg;

			fprintf(stderr, "Clock event callback function [%d] added\n", i);

			return RTN_SUCCESS;
		}
	}

	// Pool full
	return RTN_GENERAL;
}

/* Delete callback function from clock callback list */
int queue_clock_cb_func_del(void (*fn) (void *))
{
	int i, t = 0;

	// If you have add a function more than one time, all records will be deleted
	for (i = 0; i < CLOCK_CB_FUNC_POOL_SIZE; i ++)
	{
		if (clock_cb_list[i].fn == fn)
		{
			clock_cb_list[i].fn = (void (*) (void *)) NULL;
			clock_cb_list[i].arg = NULL;
			t ++;
		}
	}

	return t;
}

/* Event loop portal */
void queue_timer_cb(void *arg)
{
	sgn ++;
	int i;
	struct event_item *curr;
	
	// Event loop
	if (active_events > 0)
	{
		for (i = 0; i < total_events; i ++)
		{
			if (event_pool[i].start_time)
			{
				curr = &event_pool[i];
				if (sgn > curr->start_time && (0 == (sgn - curr->start_time) % curr->interval))
				{
					// Item run
					runner_call(curr->state, curr->func, curr->client_id, curr->params, curr->plen);
					
					// Check remainning loops
					if (curr->loop > 1)
					{
						curr->loop -= 1;
					}
					
					else if (1 == curr->loop)
					{
						queue_del_item(i);
					}
				}
			}
		}
	}
	
	return;
}

/* Add an event item into table */
int queue_add_item(int loop, int interval, lua_State *state, int client_id, char *func, char **params, int plen)
{
	int i;
	int item_id = -1;
	
	// Check interval time
	if (interval <= 0)
	{
		interval = 30;
	}
	
	// Zero loop for quit
	if (0 == loop)
	{
		return item_id;
	}
	
	// Find an empty slot
	for (i = 0; i < total_events; i ++)
	{
		if (0 == event_pool[i].start_time)
		{
			// Save data here
			event_pool[i].item_id = i;
			event_pool[i].start_time = sgn;
			event_pool[i].loop = loop;
			event_pool[i].interval = interval;
			event_pool[i].state = state;
			event_pool[i].client_id = client_id;
			event_pool[i].params = params;
			event_pool[i].plen = plen;
			strncpy(event_pool[i].func, func, MAX_FUNC_LENGTH);
			
			item_id = i;
			
			break;
		}
	}
	
	if (item_id < 0)
	{
		// Enlarge pool size
		struct event_item *new = realloc(event_pool, total_events * 2 * sizeof(struct event_item));
		
		if (!new)
		{
			return RTN_MEMORY_ERROR;
		}
		
		// Save date to new block
		event_pool = new;
		
		event_pool[total_events].item_id = total_events;
		event_pool[total_events].start_time = sgn;
		event_pool[total_events].loop = loop;
		event_pool[total_events].interval = interval;
		event_pool[total_events].state = state;
		event_pool[total_events].client_id = client_id;
		event_pool[total_events].params = params;
		event_pool[total_events].plen = plen;
		strncpy(event_pool[total_events].func, func, MAX_FUNC_LENGTH);
		
		item_id = total_events;
		
		total_events *= 2;
	}
	
	active_events += 1;
	
	return item_id;
}

/* Delete an event item from table */
void queue_del_item(int item_id)
{
	int i;
	
	if (item_id < total_events && event_pool[item_id].start_time > 0)
	{
		event_pool[item_id].start_time = 0;
		event_pool[item_id].func[0] = 0x0;
		
		for (i = 0; i < event_pool[item_id].plen; i ++)
		{
			if (event_pool[item_id].params[i])
			{
				free(event_pool[item_id].params[i]);
			}
		}
		
		if (event_pool[item_id].params)
		{
			free(event_pool[item_id].params);
		}
		
		active_events -= 1;
	}
	
	return;
}
